package actionbean;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.NoSuchElementException;

import net.sourceforge.stripes.action.HandlesEvent;
import net.sourceforge.stripes.action.RedirectResolution;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.action.SimpleMessage;
import controller.BidCtrl;
import controller.CourseCtrl;
import controller.RoundCtrl;
import controller.SectionStudentCtrl;
import controller.StudentCtrl;
import controller.validator.BidValidator;
import controller.validator.CommonValidator;
import controller.validator.Validator;
import entity.Bid;
import entity.Course;
import entity.Round;
import entity.Section;
import exception.InputValidationErrors;

public abstract class BaseBidActionBean extends BaseActionBean {

	// declare parameters
	private String courseCode;
	private String sectionCode;

	private Course course;

	// init controllers
	private CourseCtrl courseCtrl = new CourseCtrl();
	private BidCtrl bidCtrl = new BidCtrl();
	private SectionStudentCtrl sectionStudentCtrl = new SectionStudentCtrl();
	private RoundCtrl roundCtrl = new RoundCtrl();

	private ArrayList<Course> courses = new ArrayList<Course>();
	private ArrayList<Section> bookmarkedSections = new ArrayList<Section>();
	private ArrayList<Bid> pendingBids = new ArrayList<Bid>();
	private ArrayList<Bid> successfulBids = new ArrayList<Bid>();
	private ArrayList<Bid> unsuccessfulBids = new ArrayList<Bid>();
	private HashMap<Section, Bid> pendingSectionsAndBids = new HashMap<Section, Bid>();
	private HashMap<Section, Bid> successfulSectionsAndBids = new HashMap<Section, Bid>();
	private HashMap<Section, Bid> unsuccessfulSectionsAndBids = new HashMap<Section, Bid>();
	private ArrayList<Course> completedCourses = new ArrayList<Course>();

	protected abstract String redirectUriAfterSuccessfulBidOperation();

	@HandlesEvent("add-bid")
	public Resolution addBid() {

		try {
			addBidForCourseCodeAndSectionCode(courseCode, sectionCode);

			addFeedbackMessage(new SimpleMessage(
					"Bid for {0} ({1}) is successful", courseCode, sectionCode));

			return new RedirectResolution(
					redirectUriAfterSuccessfulBidOperation());

		} catch (InputValidationErrors e) {

			setGlobalValidationError(e);

			loadData();

			return getContext().getSourcePageResolution();
		}

	}

	@HandlesEvent("edit-bid")
	public Resolution editBid() {

		try {
			editBidForCourseCodeAndSectionCode(courseCode, sectionCode);

			addFeedbackMessage(new SimpleMessage(
					"Bid update for {0} ({1}) is successful", courseCode,
					sectionCode));

			return new RedirectResolution(
					redirectUriAfterSuccessfulBidOperation());

		} catch (InputValidationErrors e) {

			setGlobalValidationError(e);

			loadData();

			return getContext().getSourcePageResolution();

		}

	}

	@HandlesEvent("delete-bid")
	public Resolution delete() {

		try {
			deleteBidForCourseCodeAndSectionCode(courseCode, sectionCode);

			addFeedbackMessage(new SimpleMessage(
					"Bid for {0} ({1}) dropped successful", courseCode,
					sectionCode));

			return new RedirectResolution(
					redirectUriAfterSuccessfulBidOperation());

		} catch (InputValidationErrors e) {

			setGlobalValidationError(e);

			loadData();

			return getContext().getSourcePageResolution();

		}

	}

	/*
	 * =========================================================================
	 * Bid Information or Status methods
	 * =========================================================================
	 */

	public double computeMinBidAmount(Section section) {
		return roundCtrl.getMinBidAmountForSection(section);
	}

	public int computeVacancyForSection(Section section) {
		return sectionStudentCtrl.computeVacancyForSection(section);
	}

	private Bid findBidForSection(Section section) {
		Bid bid = pendingSectionsAndBids.get(section);

		if (bid == null) {
			bid = successfulSectionsAndBids.get(section);
		}

		if (bid == null) {
			bid = unsuccessfulSectionsAndBids.get(section);
		}

		return bid;
	}

	public double getBidAmountForSection(Section section) {
		Bid bid = findBidForSection(section);

		if (bid != null) {
			return bid.getBidAmount();
		}

		return 0;
	}

	public int getBidRoundNoForSection(Section section) {
		Bid bid = findBidForSection(section);

		if (bid != null) {
			return bid.getBidRoundNo();
		}

		return 0;
	}

	public String getBidStatus(Section section) {

		if (successfulSectionsAndBids.containsKey(section)) {
			return "isSuccessful";
		} else if (pendingSectionsAndBids.containsKey(section)) {
			return "isPending";
		} else if (unsuccessfulSectionsAndBids.containsKey(section)) {
			return "isUnsuccessful";
		} else if (bookmarkedSections.contains(section)) {
			return "isBookmarked";
		}

		return "";
	}

	public String getBidStatusForBid(Bid bid) {

		if (bid.getBidResult().equals(Bid.IN)) {
			return "isSuccessful";
		} else if (bid.getBidResult().equals(Bid.PENDING)) {
			return "isPending";
		} else if (bid.getBidResult().equals(Bid.OUT)) {
			return "isUnsuccessful";
		} else if (bookmarkedSections.contains(bid.getSection())) {
			return "isBookmarked";
		}

		return "";
	}

	public boolean canShowDropBid() {
		if (!isCurrentRoundOpen()) {
			return false;
		}

		return true;
	}

	public String getBiddingEligibilityStatusForSection(Section section) {
		CommonValidator commonValidator = new CommonValidator();

		try {
			commonValidator.checkForVacancy(section);
		} catch (Exception e) {
			return "No vacancy";
		}

		return "";
	}

	public String getBiddingEligibilityStatusForCourse(Course course) {

		/*
		 * if round is closed.
		 */
		if (!isCurrentRoundOpen()) {
			return "Round is closed";
		}

		/*
		 * if current round is round #1 and course is not from own school
		 */
		if (getCurrentRound().getRoundNo() == Round.ROUND_ONE
				&& !getLoggedInStudent().getSchool().equalsIgnoreCase(
						course.getSchool())) {
			return "Not from own school";
		}

		CommonValidator commonValidator = new CommonValidator();

		/*
		 * if user has already enrolled in this course
		 */
		try {
			commonValidator.checkForPriorCourseEnrollment(course,
					getLoggedInUserId());
		} catch (Exception e) {
			return "Course enrolled";
		}

		/*
		 * if user has completed this course
		 */
		try {
			commonValidator.checkForPriorCourseCompletion(getLoggedInUserId(),
					course.getCourseCode());
		} catch (Exception e) {
			return "Course completed";
		}

		/*
		 * if another section for this course is already bid
		 */
		try {
			commonValidator.checkForPriorBid(getLoggedInUserId(),
					course.getCourseCode());
		} catch (Exception e) {
			return "Another section bid";
		}

		try {
			commonValidator.checkForPreRequisitesCompletion(
					getLoggedInUserId(), course.getCourseCode());
		} catch (Exception e) {
			return "Incomplete prerequisite";
		}

		return "";
	}

	public String getCompletionStatusForPrerequisite(Course prerequisiteCourse) {
		if (completedCourses.contains(prerequisiteCourse)) {
			return "success";
		}

		return "danger";
	}

	public boolean isBookmarked(Section section) {
		return bookmarkedSections.contains(section);
	}

	/*
	 * =========================================================================
	 * Bid Operations
	 * =========================================================================
	 */

	protected void addBidForCourseCodeAndSectionCode(String courseCode,
			String sectionCode) throws InputValidationErrors {

		String bidAmountStr = getBidAmountStrFromParameterForCourseCodeAndSectionCode(
				courseCode, sectionCode);

		BidValidator bidValidator = new BidValidator(getLoggedInUserId(),
				courseCode, sectionCode, bidAmountStr);
		bidValidator.performValidationBeforeAdding();

		Bid bid = bidValidator.getBid();
		bidCtrl.add(bid);
	}

	protected void editBidForCourseCodeAndSectionCode(String courseCode,
			String sectionCode) throws InputValidationErrors {

		String bidAmountStr = getBidAmountStrFromParameterForCourseCodeAndSectionCode(
				courseCode, sectionCode);

		bidCtrl.updateBid(getLoggedInUserId(), courseCode, sectionCode,
				bidAmountStr);
	}

	protected void deleteBidForCourseCodeAndSectionCode(String courseCode,
			String sectionCode) throws InputValidationErrors {

		try {
			bidCtrl.deleteBid(getLoggedInUserId(), courseCode, sectionCode);
		} catch (InputValidationErrors e) {
			if (e.toJsonArray().size() == 1
					&& e.getMessage().equalsIgnoreCase(Validator.NO_SUCH_BID)) {
				// no such bid record, because the bid is a
				// successful bid
			} else {
				// propogate other errors that may have arouse
				throw e;
			}
		}

		try {
			sectionStudentCtrl.dropSectionForStudent(courseCode, sectionCode,
					getLoggedInUserId());
		} catch (InputValidationErrors e) {
			if (e.toJsonArray().size() == 1
					&& e.getMessage().equalsIgnoreCase(
							Validator.NO_SUCH_ENROLLMENT_RECORD)) {
				// no such enrollment record, because the bid is not a
				// successful bid
			} else {
				// propogate other errors that may have arouse
				throw e;
			}
		}

	}

	/*
	 * =========================================================================
	 * Data loaders
	 * =========================================================================
	 */

	protected void loadData() {
		loadBookmarkedSectionsForLoggedInStudent();
		loadAllBidsForLoggedInStudent();
		loadCompletedCoursesForLoggedInStudent();
	}

	private void loadBookmarkedSectionsForLoggedInStudent() {
		bookmarkedSections = retrieveBookmarkedSectionsForLoggedInStudent();
	}

	private void loadAllBidsForLoggedInStudent() {
		ArrayList<Bid> bids = retrieveAllBidsForLoggedInStudent();

		for (Bid eachBid : bids) {
			if (eachBid.getBidResult().equals(Bid.IN)) {
				successfulBids.add(eachBid);
				successfulSectionsAndBids.put(eachBid.getSection(), eachBid);
			} else if (eachBid.getBidResult().equals(Bid.PENDING)) {
				pendingBids.add(eachBid);
				pendingSectionsAndBids.put(eachBid.getSection(), eachBid);
			} else {
				unsuccessfulBids.add(eachBid);
				unsuccessfulSectionsAndBids.put(eachBid.getSection(), eachBid);
			}
		}

	}

	private void loadCompletedCoursesForLoggedInStudent() {
		StudentCtrl studentCtrl = new StudentCtrl();
		completedCourses = studentCtrl
				.retrieveCompletedCoursesForStudent(getLoggedInUserId());
	}

	/*
	 * =========================================================================
	 * Getters & Setters
	 * =========================================================================
	 */

	public String getCourseCode() {
		return courseCode;
	}

	public String getSectionCode() {
		return sectionCode;
	}

	public Course getCourse() {
		return course;
	}

	public ArrayList<Course> getCourses() {
		return courses;
	}

	public ArrayList<Section> getBookmarkedSectionsForLoggedInStudent() {
		return bookmarkedSections;
	}

	public ArrayList<Section> getSuccessfullyEnrolledSectionsForLoggedInStudent() {
		ArrayList<Section> sections = new ArrayList<Section>(
				successfulSectionsAndBids.keySet());
		return sections;
	}

	public ArrayList<Section> getUnsuccessfulSectionsForLoggedInStudent() {
		ArrayList<Section> sections = new ArrayList<Section>(
				unsuccessfulSectionsAndBids.keySet());
		return sections;
	}

	public ArrayList<Section> getPendingSectionsForLoggedInStudent() {
		ArrayList<Section> pendingSections = new ArrayList<Section>(
				pendingSectionsAndBids.keySet());
		return pendingSections;
	}

	public ArrayList<Bid> getSuccessfulBidsForLoggedInStudent() {
		return successfulBids;
	}

	public ArrayList<Bid> getUnsuccessfulBidsForLoggedInStudent() {
		return unsuccessfulBids;
	}

	public ArrayList<Bid> getPendingBidsForLoggedInStudent() {
		return pendingBids;
	}

	public void setCourseCode(String courseCode) {
		this.courseCode = courseCode;
		try {
			this.course = courseCtrl.retrieveCourseForCourseCode(courseCode);
		} catch (NoSuchElementException e) {
		}
	}

	public void setSectionCode(String sectionCode) {
		this.sectionCode = sectionCode;
	}

	private String getBidAmountStrFieldNameForCourseCodeAndSectionCode(
			String courseCode, String sectionCode) {
		return "bidAmount_" + courseCode + "_" + sectionCode;
	}

	private String getBidAmountStrFromParameterForCourseCodeAndSectionCode(
			String courseCode, String sectionCode) {
		String fieldName = getBidAmountStrFieldNameForCourseCodeAndSectionCode(
				courseCode, sectionCode);
		return getContext().getRequest().getParameter(fieldName);
	}

}
